﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Nate_sRoguelikeGameXNA.GameObjects.Architecture;

namespace Nate_sRoguelikeGameXNA
{
    class Corridor
    {
        public List<Rectangle> Rectangles = new List<Rectangle>();
        private List<MapBlockPathInfo> _path = new List<MapBlockPathInfo>();
        private Door _sourceDoor, _destDoor;
        private GameArchitecture _architecture;
        public Corridor(GameArchitecture architecture, Door sourceDoor, Door destDoor)
        {
            _architecture = architecture;
            _sourceDoor = sourceDoor;
            _destDoor = destDoor;

            FindPath();
        }

        public void AddToMap(MapBlock[][] map)
        {
            foreach (var step in _path)
                step.MapBlock.Architecture = _architecture;
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            if (Game.Debug)
            {
                Message message = new Message();
                _sourceDoor.Room.OuterPerimeter.Draw(spriteBatch, '$', Color.Purple);
                _destDoor.Room.OuterPerimeter.Draw(spriteBatch, '$', Color.Blue);
                foreach (var step in _path)
                {
                    message.Text = "O";
                    message.Location = Game.GetScreenCoordsWithOffset(step.MapBlock.Location.X, step.MapBlock.Location.Y);
                    message.Color = Color.Green;
                    message.Draw(spriteBatch);
                }
            }
        }

        public bool FindPath()
        {
            int iterationCount = 0;
            MapBlock sourceBlock = _sourceDoor.Room.Level.GetMapBlockAt(_sourceDoor.Outside);
            MapBlock destBlock = _destDoor.Room.Level.GetMapBlockAt(_destDoor.Outside);
            MapBlockPathInfo currentPathingInfo = new MapBlockPathInfo(sourceBlock, null, destBlock, iterationCount);
            HashSet<MapBlock> closedList = new HashSet<MapBlock>();
            List<MapBlockPathInfo> closedPathingInfo = new List<MapBlockPathInfo>();
            HashSet<MapBlock> openBlocks = new HashSet<MapBlock>();
            List<MapBlockPathInfo> openBlockPathingInfo = new List<MapBlockPathInfo>();
            Dictionary<MapBlock, MapBlockPathInfo> blockToPathingInfo = new Dictionary<MapBlock, MapBlockPathInfo>();
            _path.Clear();

            while (currentPathingInfo.MapBlock != destBlock)
            {
                iterationCount++;
                // Add the current block to the closed list
                closedList.Add(currentPathingInfo.MapBlock);
                closedPathingInfo.Add(currentPathingInfo);

                // Remove it from the open lists
                openBlocks.Remove(currentPathingInfo.MapBlock);
                openBlockPathingInfo.Remove(currentPathingInfo);
                
                // Get the surrounding blocks
                HashSet<MapBlock> currentList =
                    new HashSet<MapBlock>(_sourceDoor.Room.Level.GetSurroundingMapBlocks(
                        currentPathingInfo.MapBlock.Location, false).Where(
                            b =>
                            !_sourceDoor.Room.OuterPerimeter.ReallyContains(b.Location) &&
                            !_destDoor.Room.OuterPerimeter.ReallyContains(b.Location)));
                // Remove blocks that are already in the closed list
                currentList.ExceptWith(closedList);
                // Remove blocks that are already in the inspection list
                HashSet<MapBlock> openAdjacentBlocks = new HashSet<MapBlock>(currentList);
                openAdjacentBlocks.IntersectWith(openBlocks);
                currentList.ExceptWith(openBlocks);

                bool resortCollection = false;
                foreach (
                    MapBlockPathInfo p in
                        openAdjacentBlocks.Select(b => blockToPathingInfo[b]).Where(
                            p =>
                            currentPathingInfo.DistanceFromStart +
                            p.CalculateDistanceFromAdjacentBlock(currentPathingInfo.MapBlock) < p.DistanceFromStart))
                {
                    p.Parent = currentPathingInfo;
                    p.CalculateDistances();
                    resortCollection = true;
                }

                if (!resortCollection)
                    resortCollection = currentList.Any();
                // Add the blocks to the inspection list
                openBlocks.UnionWith(currentList);
                // Get the pathing info for the blocks
                foreach (MapBlock b in currentList)
                {
                    MapBlockPathInfo info = new MapBlockPathInfo(b, currentPathingInfo, _destDoor.MapBlock, iterationCount);
                    openBlockPathingInfo.Add(info);
                    blockToPathingInfo.Add(b, info);
                }
                // Sort the open lists by distance
                if (resortCollection)
                    openBlockPathingInfo.Sort();

                if (!openBlockPathingInfo.Any())
                    return false;
                currentPathingInfo = openBlockPathingInfo[0];
            }
            var step = currentPathingInfo; 
            while (step != null)
            {
                _path.Add(step);
                step = step.Parent;
            }
            return true;
        }
    }
}
